package com.jxpanda.infrastructure.core.tollkit;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.jxpanda.infrastructure.core.constant.StringConstant;
import lombok.Builder;
import lombok.Getter;
import lombok.SneakyThrows;

import java.lang.reflect.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 反射相关工具类
 *
 * @author Panda
 */
public class ReflectionKit {

    private static final String GET_PREFIX = "get";
    private static final String GET_CLASS = "getClass";
    private static final String SET_PREFIX = "set";
    private static final String IS_PREFIX = "is";

    private static final Map<Class<?>, Object> DEFAULT_VALUE_MAP = new HashMap<>() {{
        put(Collection.class, Collections.emptyList());
        put(List.class, Collections.emptyList());
        put(Set.class, Collections.emptyList());
        put(Map.class, Collections.emptyMap());
        put(Object.class, new Object());
        put(Byte.class, 0);
        put(Short.class, 0);
        put(Integer.class, 0);
        put(Long.class, 0L);
        put(Float.class, 0.0F);
        put(Double.class, 0.0);
        put(BigDecimal.class, BigDecimal.ZERO);
        put(Boolean.class, false);
        put(String.class, StringConstant.BLANK);
    }};

    /**
     * 获取字段类型
     *
     * @param clazz     实体类型
     * @param fieldName 字段名称
     * @return 字段的类型
     */
    @SneakyThrows
    public static Class<?> getFieldType(Class<?> clazz, String fieldName) {
        Field field = getFieldMap(clazz).get(fieldName);
        return field == null ? null : field.getType();
    }

    /**
     * 获取字段值
     *
     * @param entity 实体
     * @param field  字段
     * @return 属性值
     */
    @SneakyThrows
    public static Object getFieldValue(Object entity, Field field) {
        field.setAccessible(true);
        return field.get(entity);
    }

    /**
     * 设置字段的值
     *
     * @param entity 实体
     * @param field  字段
     */
    @SneakyThrows
    public static void setFieldValue(Object entity, Field field, Object value) {
        field.setAccessible(true);
        field.set(entity, value);
    }

    /**
     * 设置字段的值
     *
     * @param entity 实体
     * @param field  字段（字符串结构）
     */
    @SneakyThrows
    public static void setFieldValue(Object entity, String field, Object value) {
        Field entityFiled = getFieldMap(entity.getClass()).get(field);
        setFieldValue(entity, entityFiled, value);
    }

    /**
     * 初始化字段
     *
     * @param entity 实体
     * @param field  字段
     */
    @SneakyThrows
    public static void initField(Object entity, Field field) {
        field.setAccessible(true);
        Class<?> fieldType = field.getType();
        Object defaultValue = DEFAULT_VALUE_MAP.get(fieldType);
        if (defaultValue == null) {
            if (fieldType.isEnum()) {
                defaultValue = EnumKit.getDefaultEnum(fieldType);
            } else {
                Constructor<?>[] constructors = fieldType.getConstructors();
                if (constructors.length > 0) {
                    defaultValue = fieldType.getConstructor().newInstance();
                }
            }
        }
        field.set(entity, defaultValue);
    }

    /**
     * <p>
     * 获取该类的所有属性列表
     * </p>
     *
     * @param clazz 反射类
     */
    public static Map<String, Field> getFieldMap(Class<?> clazz) {
        return getFieldList(clazz).stream().collect(Collectors.toMap(Field::getName, Function.identity()));
    }

    /**
     * <p>
     * 获取该类的所有属性列表
     * </p>
     *
     * @param clazz 反射类
     */
    public static List<Field> getFieldList(Class<?> clazz) {

        Field[] fields = clazz.getDeclaredFields();

        Field[] superFields = {};

        if (clazz.getSuperclass() != null) {
            superFields = clazz.getSuperclass().getDeclaredFields();
        }

        return CollectionKit.distinctMerge(Field::getName, fields, superFields).stream()
                /* 过滤静态属性 */
                .filter(f -> !Modifier.isStatic(f.getModifiers()))
                /* 过滤 transient关键字修饰的属性 */
                .filter(f -> !Modifier.isTransient(f.getModifiers()))
                .collect(Collectors.toList());

    }

    /**
     * <p>
     * 获取该类的所有getter函数
     * </p>
     *
     * @param clazz 反射类
     */
    public static Map<String, Method> getGetterMap(Class<?> clazz) {
        Method[] methods = clazz.getMethods();
        Map<String, Field> fieldMap = getFieldMap(clazz);
        Map<String, Method> result = new HashMap<>(fieldMap.size());
        for (Method method : methods) {
            String methodName = method.getName();
            boolean isGetter = !Modifier.isStatic(method.getModifiers())
                               && !methodName.equals(GET_CLASS)
                               && (methodName.startsWith(GET_PREFIX) || methodName.startsWith(IS_PREFIX));
            if (isGetter) {
                String fieldName = StringKit.uncapitalize(method.getName()
                        .replaceFirst("^" + GET_PREFIX, "")
                        .replaceFirst("^" + IS_PREFIX, ""));
                if (fieldMap.containsKey(fieldName)) {
                    result.put(fieldName, method);
                }
            }
        }
        return result;
    }

    /**
     * <p>
     * 获取该类的所有setter函数
     * </p>
     *
     * @param clazz 反射类
     */
    public static Map<String, Method> getSetterMap(Class<?> clazz) {
        Method[] methods = clazz.getMethods();
        Map<String, Field> fieldMap = getFieldMap(clazz);
        Map<String, Method> result = new HashMap<>(fieldMap.size());
        for (Method method : methods) {
            if (!Modifier.isStatic(method.getModifiers()) && method.getName().startsWith(SET_PREFIX)) {
                String fieldName = StringKit.uncapitalize(method.getName().replaceFirst(SET_PREFIX, ""));
                if (fieldMap.containsKey(fieldName)) {
                    result.put(fieldName, method);
                }
            }
        }
        return result;
    }


    public static Map<String, String> objectToStringMap(Object object) {
        return objectToMap(object, ObjectConverter.stringConverter());
    }

    public static Map<String, String> objectToMapUsingJacksonProperty(Object object) {
        return objectToMap(object, ObjectConverter.stringConverterUsingJacksonProperty());
    }

    public static <V> Map<String, V> objectToMap(Object object) {
        return objectToMap(object, ObjectConverter.defaultConverter());
    }

    public static <K, V> Map<K, V> objectToMap(Object object, ObjectConverter<K, V> objectConverter) {
        Field[] fields = object.getClass().getDeclaredFields();
        Map<K, V> map = new HashMap<>(fields.length);
        for (Field field : fields) {
            try {
                K key = objectConverter.getKeyConverter().apply(field);
                V value = objectConverter.getValueConverter().apply(getFieldValue(object, field.getName()));
                map.put(key, value);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return map;
    }

    public static Object getFieldValue(Object object, String fieldName) {
        return getFieldValue(object, fieldName, null);
    }

    public static <T> T getFieldValue(Object object, String fieldName, Class<T> valueType) {
        try {
            Class<?> clazz = valueType == null ? object.getClass() : valueType;
            Method method;
            if (clazz.isRecord()) {
                method = clazz.getDeclaredMethod(fieldName);
            } else {
                method = clazz.getDeclaredMethod("get" + StringKit.capitalize(fieldName));
            }
            return cast(method.invoke(object));
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            return null;
        }
    }


    public static <T> T newInstance(Class<T> clazz) {
        try {
            Constructor<T> constructor = clazz.getConstructor();
            constructor.setAccessible(true);
            return constructor.newInstance();
        } catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
                 InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T newInstance(Class<T> clazz, Class<?>[] paramTypes, Object[] params) {
        try {
            Constructor<T> constructor = clazz.getConstructor(paramTypes);
            constructor.setAccessible(true);
            return constructor.newInstance(params);
        } catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
                 InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> T cast(Object object) {
        return (T) object;
    }


    @Getter
    @Builder
    public static class ObjectConverter<K, V> {
        private Function<Field, K> keyConverter;
        private Function<Object, V> valueConverter;

        public static <V> ObjectConverter<String, V> defaultConverter() {
            return ObjectConverter.<String, V>builder()
                    .keyConverter(defaultKeyConverter())
                    .valueConverter(defaultValueConverter())
                    .build();
        }

        public static ObjectConverter<String, String> stringConverter() {
            return ObjectConverter.<String, String>builder()
                    .keyConverter(defaultKeyConverter())
                    .valueConverter(stringValueConverter())
                    .build();
        }

        public static ObjectConverter<String, String> stringConverterUsingJacksonProperty() {
            return ObjectConverter.<String, String>builder()
                    .keyConverter(jacksonKeyConverter())
                    .valueConverter(stringValueConverter())
                    .build();
        }


        public static Function<Field, String> defaultKeyConverter() {
            return Field::getName;
        }

        public static Function<Field, String> jacksonKeyConverter() {
            return field -> {
                JsonProperty annotation = field.getAnnotation(JsonProperty.class);
                if (annotation != null) {
                    return annotation.value();
                }
                return field.getName();
            };
        }

        public static <V> Function<Object, V> defaultValueConverter() {
            return ReflectionKit::cast;
        }

        public static Function<Object, String> stringValueConverter() {
            return value -> value == null ? StringConstant.BLANK : value.toString();
        }

    }

}
